package concurrent;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;

/**
 * Verarbeitet eine mitgelieferte .xml, schreibt daraus generierte Werte in Variablen des OberParsers
 * @author Lukas Subel, Patrick Bender
 */
public class Parser implements Runnable{
	
	/**
	 * Menge der Kanten;
	 * speichert für jeden KnotenID die Menge an KnotenID's, auf die dieses KnotenID zeigt
	 */
	private HashMap<Integer,Set<Integer>> edgeMap = new HashMap<Integer,Set<Integer>>();
	//Eigene Variablen
	String[] tokenArray;	
	private String data;
	
	//Nebenlaeufig benutze Variablen
	private CyclicBarrier barrier;
	private HashMap<Integer,Node> idNodeMap;
	private HashMap<Integer,Masche> capacityMascheMap;
	private CountDownLatch parserLatch;
	
	/**
	 * Konstruktor; Dient zur uebergabe der nebenlauefig benutzten Variablen
	 * @param data data String mit dem zu parsenden text
	 * @param idNodeMap gemeinsame HashMap; KnotenID -> Knoten
	 * @param capacityMascheMap gemeinsame HashMap; Capacity -> Masche
	 * @param barrier gemeinsame Barrier; benötigt, um zu gewährleisten, dass erst Kanten eingetragen werden, wenn alle Knoten geparst wurden
	 * @param parserLatch übergreifende ParserLatch, bei dem man anhalten soll, damit der Oberparser erst nach Vollendung weitermacht 
	 */
	public Parser(String data, HashMap<Integer,Node> idNodeMap, HashMap<Integer, Masche> capacityMascheMap, CyclicBarrier barrier, CountDownLatch parserLatch) {
		this.data = data;
		this.idNodeMap = idNodeMap;
		this.barrier = barrier;
		this.capacityMascheMap = capacityMascheMap;
		this.parserLatch = parserLatch;
	}
	
	/**
	 * Parser; Bekommt einen data String zur bearbeitung und parsed diesen
	 */
	@Override public void run() { 
		try {
			//prepare the incoming string for parsing
			prepare();
			//start to parse
			parse();
			//Hier muessen wir warten um sicherzustellen das alle Threads mit dem parsen fertig
			//sind damit auch sicher alle Knoten (auch die der anderen Threads) vorhanden sind
			barrier.await();
			//start to add the edges to the nodes
			setEdges();
			//setzt den Counter um eins runter
			parserLatch.countDown();
		 }
		 catch (Exception e) {
			// Im Fehlerfall den Fehler ausgeben und Terminieren
			e.printStackTrace();
			System.exit(-1);
		 }
	}

	private void prepare() {
		data = data.replaceAll("\n", "");
		data = data.replaceAll("\r", "");
		data = data.replaceAll(" ", "");
		tokenArray = data.split(">");
	}

	/**
	 * Parse the nodes out of the .xml.
	 * Safe the extracted edge at the variable edgeMap
	 * @throws Exception if there is an broken .xml
	 */
	private void parse() throws Exception{
	
	//Variablen fuer Knoten
	int 		capacity 		= 0;
	boolean 	capacityValid 	= false;
	boolean 	capacityInc 	= false;
	int 		id			 	= 0;
	boolean	 	idValid 		= false;
	boolean 	idInc 			= false;
	Node 		node 			= null;

	//Variablen fuer Kanten
	int 		src 			= 0;
	boolean 	srcidValid 		= false;
	boolean 	srcidInc 		= false;
	int 		dst 			= 0;
	boolean 	dstidValid 		= false;
	boolean 	dstidInc 		= false;
	boolean 	edge 			= false;
		
	//counter for Tokens
	int i = 0;
		
	//Fallunterschiedung
		for(String line : tokenArray)
		{
				i++;
				// <node>
	      		if (line.equals("<node")) {
	      			if (node != null) throw new Exception("Ein <node> kann nicht in einem <node> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (edge) throw new Exception("Ein <node> kann nicht in einem <edge> stehen [on Token " + i + " (" + line + ">) ]");
	      			node = new Node();
	      		}
				// <id>
	      		else if ( line.equals("<id") ) {
	      			if (node == null) throw new Exception("Ein <id> kann erst nach einem <node> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (idInc) throw new Exception("Ein <id> kann nicht in einem <id> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (capacityInc) throw new Exception("Ein <id> kann nicht in einem <capacity> stehen [on Token " + i + " (" + line + ">) ]");
	      			idInc = true;
	      		}
				// </id>
	      		else if ( line.endsWith("</id") ) {
	      			if (!(idInc)) throw new Exception("</id> ohne <id> [on Token " + i + " (" + line + ">) ]");
	      			try { id = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gueltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			idValid = true;
	      			idInc = false;
	      		}
				// <capacity>
	      		else if ( line.equals("<capacity") ) {
	      			if (node == null) throw new Exception("Ein <capacity> kann erst nach einem <node> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (capacityInc) throw new Exception("Ein <capacity> kann nicht in einem <capacity> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (idInc) throw new Exception("Ein <capacity> kann nicht in einem <id> stehen [on Token " + i + " (" + line + ">) ]");
	      			capacityInc = true;
	      		}
				// </capacity>
	      		else if ( line.endsWith("</capacity") ) {
	      			if (!(capacityInc)) throw new Exception("</capacity> ohne <capacity> [on Token " + i + " (" + line + ">) ]");
	      			try { capacity = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gueltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			capacityValid = true;
	      			capacityInc = false;
	      		}
				// </node>
	      		else if ( line.equals("</node") ) {
	      			if (!(node != null && capacityValid && idValid)) throw new Exception("Parameter von <node> nicht alle gesetzt oder fehlendes <node> [on Token " + i + " (" + line + ">) ]");
	      			//baue neuen Knoten
	      			node.setId(id);
	      			node.setCapacity(capacity);
	      			//füge neuen Knoten zur KnotenMap zu
	      			synchronized (idNodeMap) {
	      				//wenn Knoten schon existiert, die Kapazität aber unterschiedlich ist,
	      				//werfe Ausnahme
		      			if (idNodeMap.get(id) != null) {
		      				if (idNodeMap.get(id).getCapacity() != capacity) throw new Exception ("<node> mit gleicher id und unterschiedlicher capacity gefunden [on Token " + i + " (" + line + ">) ]");
		      			}
		      			//sonst trage Knoten in die Map ein
		      			else {
			      			idNodeMap.put(id,node);
			    			//wenn eine Masche mit selber Kapazität schon existiert
			      			if (capacityMascheMap.containsKey(capacity))
			    			{
			      				//trage neuen Knoten in diese Masche ein
			    				Masche targetMasche = capacityMascheMap.get(capacity);
			    				targetMasche.add(node);
			    			}
			    			else 
			    			{
			    				//erstelle neue Masche, schreibe Knoten und Kapazität in die Masche
			    				Masche targetMasche = new Masche();
			    				//knoten zur masche hinzufuegen
			    				targetMasche.add(node);
			    				//trage neue Masche in die MascheMap ein
			    				capacityMascheMap.put(capacity, targetMasche);
			    			}	
		      			}
	      			}
	      			//setze Variablen zurück
	      			idValid = false;
	      			capacityValid = false;
	      			node = null;
	      		}
	      	  	// <edge>
	      		else if (line.equals("<edge")) {
					if (edge) throw new Exception("Ein <edge> kann nicht in einem <edge> stehen [on Token " + i + " (" + line + ">) ]");
					if (node != null) throw new Exception("Ein <edge> kann nicht in einem <node> stehen [on Token " + i + " (" + line + ">) ]");
					edge = true;
				}
				// <srcid>
	      		else if ( line.equals("<srcid") ) {
	      			if (!edge) throw new Exception("Ein <srcid> kann erst nach einem <edge> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (srcidInc) throw new Exception("Ein <srcid> kann nicht in einem <srcId> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (dstidInc) throw new Exception("Ein <srcid> kann nicht in einem <dstId> stehen [on Token " + i + " (" + line + ">) ]");
	      			srcidInc = true;
	      		}
				// </srcid>
	      		else if ( line.endsWith("</srcid") ) {
	      			if (!(srcidInc)) throw new Exception("</srcid> ohne <srcid> [on Token " + i + " (" + line + ">) ]");
	      			try { src = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gueltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			srcidInc = false;
	      			srcidValid = true;
	      		}
				// <dstid>
	      		else if ( line.equals("<dstid") ) {
	      			if (!edge) throw new Exception("Ein <dstid> kann erst nach einem <edge> kommen [on Token " + i + " (" + line + ">) ]");
	      			if (dstidInc) throw new Exception("Ein <dstid> kann nicht in einem <dstId> stehen [on Token " + i + " (" + line + ">) ]");
	      			if (srcidInc) throw new Exception("Ein <dstid> kann nicht in einem <srcId> stehen [on Token " + i + " (" + line + ">) ]");
	      			dstidInc = true;
	      		}
				// </dstid>
	      		else if ( line.endsWith("</dstid") ) {
	      			if (!(dstidInc)) throw new Exception("</dstid> ohne <dstid> [on Token " + i + " (" + line + ">) ]");
	      			try { dst = Integer.valueOf(line.substring(0,line.indexOf('<'))) ; }
	      			catch (NumberFormatException e) { throw new Exception("" + line.substring(0,line.indexOf('<')) + " ist keine gueltige Nummer [on Token " + i + " (" + line + ">) ]"); }
	      			dstidInc = false;
	      			dstidValid = true;
	      		}
				// </edge>
	      		else if ( line.equals("</edge") ) {
	      			if (!(edge && srcidValid && dstidValid)) throw new Exception("Parameter von <edge> nicht alle gesetzt oder fehlendes <edge> (on line " + i + ")");
	      			if (edgeMap.get(src) == null) edgeMap.put(src, new HashSet<Integer>());
	      			//füge Zielknoten zur Menge vom Startknoten hinzu
	      			edgeMap.get(src).add(dst);
	      			//setze Variablen zurück
	      			srcidValid = false;
	      			dstidValid = false;
	      			edge = false;
	      		}
				// tokens der node phase
	  	    	else if (line.equals("</graph") || line.equals("<graph") || line.equals("") ) continue;
	  	    	// sonstiges
	  	    	else throw new Exception("Unerwartete Zeichenfolge! [on Token " + i + " (" + line + ">) ]");
				
				
		
	      	}
		
		//check if we finally reached the graph end and everything is good
		if (idInc) throw new Exception("Fehlendes </id> am Ende der Datei");
		if (capacityInc) throw new Exception("Fehlendes </capacity> am Ende der Datei");
		if (srcidInc) throw new Exception("Fehlendes </srcid> am Ende der Datei");
		if (dstidInc) throw new Exception("Fehlendes </dstid> am Ende der Datei");
		if (node != null) throw new Exception("Fehlendes </node> am Ende der Datei");
		if (edge) throw new Exception("Fehlendes </edge> am Ende der Datei");
		
		}
	
	/**
	 * Läuft über die im Parsing Prozess abgespeicherten Kanten,
	 * die der Parser aus seiner .xml extrahiert hat, 
	 * und speichert sie in den passenden Knoten
	 */
	private void setEdges() {
		Node src = null;
		Node dst = null;
		
		//laufe über alle gespeicherten Kanten
		for (Entry<Integer, Set<Integer>> entry : edgeMap.entrySet()) {
			
			//hole den Startknoten aus der KnotenHashMap
			src = idNodeMap.get(entry.getKey());
			//iteriere über alle KnotenID's, auf die der Startknoten zeigt
			for (int dstid : entry.getValue()) {
				//hole dir den zuständigen Zielknoten
				dst = idNodeMap.get(dstid);
				//trage den Startknoten in den Predecessor der Zielknoten sein
				synchronized (dst) {
					dst.addPred(src);
				}
			}
		}
	}
	
}